package internal

import (
	"fmt"
	"github.com/name5566/leaf/gate"
	"lol.com/feb214-game/nest.git/leaf"
	"lol.com/feb214-game/nest.git/log"
	"lol.com/feb214-game/nest.git/proto"
	"lol.com/feb214-game/nest.git/tools/tz"
	"lol.com/feb214-game/nest.git/user"
	"sync/atomic"
	"talent.com/cube/common/cxt"
	"talent.com/cube/common/msg"
	"talent.com/cube/conf"
	"talent.com/cube/conf/g"
	"talent.com/cube/modules/game/internal/table"
)

func authUser(req *msg.AuthReq, ag gate.Agent) (allow bool, info *user.MixedInfo) {
	if !conf.ServerConfig().IsEnabled(req.GameType) {
		leaf.CloseAgent(ag, proto.STATUS_PARAM_ERROR, "game type invalid", req.UserID)
		return
	}
	log.Info("%d, %s", req.UserID, req.Token)
	roomConf := conf.RoomConfig(req.GameType, req.RoomKind)
	if roomConf == nil || roomConf.ID != req.RoomKind || !roomConf.See {
		leaf.CloseAgent(ag, proto.STATUS_PARAM_ERROR, "room invalid", req.UserID)
		return
	}
	var err error
	info = &user.MixedInfo{}
	dao := g.GetAccountDAO(req.GameType)
	info.ClientInfo, err = dao.CheckUserTokenInfo(req.UserID, req.Token)
	if err != nil {
		leaf.CloseAgent(ag, proto.STATUS_UNKNOWN_ERROR, err.Error(), req.UserID)
		return
	}
	if info.ClientInfo == nil {
		leaf.CloseAgent(ag, proto.STATUS_TOKEN_INVALID, "", req.UserID)
		return
	}
	// 获取用户账户数据
	info.Account, err = dao.GetUser(req.UserID)
	if info.Account == nil || err != nil {
		leaf.CloseAgent(ag, proto.STATUS_UNKNOWN_ERROR, "account invalid", req.UserID)
		return
	}
	return true, info
}

//reconnect返回true表示不必继续登录，否则走正常登录逻辑
func reconnect(req *msg.AuthReq, ag gate.Agent, info *user.MixedInfo) (ok bool, old *cxt.UserContext) {
	var (
		tc  *cxt.TableContext
		uc  *cxt.UserContext
		tmp interface{}
	)
	// 如果用户在内存中，尝试重连
	if tmp, ok = g.AllAgents.Load(req.UserID); ok {
		pre, _ := tmp.(gate.Agent)
		//网络波动，导致客户端连发两次认证请求
		if pre == ag {
			if uc, ok = pre.UserData().(*cxt.UserContext); ok {
				tc = cxt.GetTableContext(uc.TableID)
				if tc != nil && tc.RoomST != nil {
					tc.RPC.Go(g.EventSyncRoomST, uc, tc)
				}
			}
			log.Debug("user %v duplicate auth", req.UserID)
			return true, nil
		}
		//真正的重连
		if uc, ok = pre.UserData().(*cxt.UserContext); ok {
			//由于服务器关闭导致的桌子关闭，不处理
			if uc.TableID == 0 && atomic.LoadInt32(&g.ServerClosed) == 1 {
				return false, uc
			}
			//由于其他异常导致的桌子关闭(桌子创建失败或者TermNum创建失败)，新的登陆需要使用原来的一些数据
			if uc.TableID == 0 {
				pre.SetUserData(nil)
				pre.Close()
				log.Info("use %v reconnect but table %v closed, resign", uc.ID, uc.TableID)
				return false, uc
			}
			//其他情况桌子不存在，应该是bug
			tc = cxt.GetTableContext(uc.TableID)
			if tc == nil {
				pre.SetUserData(nil)
				pre.Close()
				log.Error("use %v reconnect but table %v not exist!!!", uc.ID, uc.TableID)
				return false, uc
			}
			//在其他游戏里
			if tc.GameType != req.GameType || tc.Kind != req.RoomKind {
				//金币锁尚未解除，用户不应该能连进来
				if !uc.CostSyncCounter.Cleared() {
					leaf.CloseAgent(ag, proto.STATUS_TOKEN_INVALID,
						fmt.Sprintf("user still in game %v room %v",
							tc.GameType, tc.Kind), req.UserID)
					return true, nil
				}
				//否则视为新登陆用户
				leaf.CloseAgent(pre, proto.STATUS_DUP_LOGIN, "", req.UserID)
				return false, nil
			}
			//否则只是关闭原来的连接，恢复登录
			ag.SetUserData(uc)
			atomic.StoreInt32(&uc.Disconnect, 0)
			//如果金币锁已经解除，需要更新余额
			if uc.CostSyncCounter.Cleared() {
				uc.Credit = info.Credit
			}
			g.AllAgents.Store(req.UserID, ag)
			pre.SetUserData(nil)
			pre.Close()
			tc.RPC.Go(g.EventSyncRoomST, uc, tc)
			log.Info("user %v restore, gameType:%v, room:%v, roomKind:%v, credit:%v",
				uc.ID, g.GetCodeLabel(req.GameType), tc.ID, tc.Kind, uc.Credit)
			return true, nil
		}
		//防止意外情况
		g.AllAgents.Delete(req.UserID)
		leaf.CloseAgent(pre, proto.STATUS_DUP_LOGIN, "", req.UserID)
		log.Warn("saved %v userData is nil", req.UserID)
	}
	return false, nil
}

func loginUser(req *msg.AuthReq, ag gate.Agent, info *user.MixedInfo) {
	imCache := g.GetPublicCache(req.GameType)

	// 添加金币🔒
	isNew, err := imCache.SaveUser(req.UserID, req.RoomKind)
	if err != nil {
		leaf.CloseAgent(ag, proto.STATUS_UNKNOWN_ERROR, err.Error(), req.UserID)
		return
	}
	if !isNew {
		//断线重连
		log.Info("user %v reconnect to server", req.UserID)
		status, err := imCache.LoadUserServer(req.UserID)
		if err != nil {
			leaf.CloseAgent(ag, proto.STATUS_UNKNOWN_ERROR, err.Error(), req.UserID)
			return
		}
		if status.Game != req.GameType {
			leaf.CloseAgent(ag, proto.STATUS_DUP_LOGIN, "", req.UserID)
			return
		}
		if status.Server != conf.ServerConfig().Server.URL {
			leaf.CloseAgent(ag, proto.STATUS_REDIRECT, status.Server, req.UserID)
			return
		}
	}
	var (
		ok  bool
		old *cxt.UserContext
	)
	// 🔗 尝试重连
	if ok, old = reconnect(req, ag, info); ok {
		return
	}
	//如果服务正在关闭，除了重连的，其他新用户禁止加入游戏
	if atomic.LoadInt32(&g.ServerClosed) == 1 {
		leaf.CloseAgent(ag, proto.STATUS_SERVER_CLOSED, "", req.UserID)
		_ = imCache.RemoveUser(req.UserID)
		return
	}
	userStats := g.StatsDAO.GetUserStats(req.UserID)
	var withdrawalRate float64 = 1
	if userStats.Recharge.Total > 0 {
		rate := g.Viper.GetFloat64("RechargeWithdrawalRate")
		withdrawalRate = rate
	} else {
		rate := g.Viper.GetFloat64("ZeroWithdrawalRate")
		withdrawalRate = rate
	}
	var gender int32
	if info.Gender {
		gender = 1
	}
	userST := &msg.UserST{
		ID:             req.UserID,
		Credit:         info.Credit,
		Gender:         gender,
		UserName:       info.UserName,
		Avatar:         info.Avatar,
		Addr:           info.Addr,
		BetInfo:        make(map[int32]*msg.ChipInfo),
		EnterAt:        tz.GetNowTs(),
		Pkg:            info.Pkg,
		WithdrawalRate: withdrawalRate,
	}
	uc := &cxt.UserContext{
		UserST:        userST,
		GameType:      req.GameType,
		RoomKind:      req.RoomKind,
		RobotLevel:    -1,
		ClientVersion: req.Version,
	}
	if old == nil {
		uc.CostSyncCounter = cxt.NewSyncCounter(uc)
	} else {
		uc.CostSyncCounter = old.CostSyncCounter
		if !uc.CostSyncCounter.Cleared() {
			log.Info("user %v gold lock not released yet, use credit %v instead of %v",
				uc.ID, old.Credit, uc.Credit)
			uc.Credit = old.Credit
		}
	}
	// 组装agent数据添加userData
	ag.SetUserData(uc)
	g.AllAgents.Store(req.UserID, ag)
	// 把用户分配到房间
	if !table.DispatchUser(uc) {
		leaf.CloseAgent(ag, proto.STATUS_UNKNOWN_ERROR, "can't dispatch user", req.UserID)
		return
	}
	tc := cxt.GetTableContext(uc.TableID)
	tc.RPC.Go(g.EventSyncRoomST, uc, tc)
	log.Info("user %v login to %s, credit:%v, client:%+v", req.UserID, g.GetCodeLabel(req.GameType),
		info.Credit, info.ClientInfo)
}

//仅在socket断开时触发
func logoutUser(uc *cxt.UserContext, tc *cxt.TableContext) {
	allow := false
	if tc == nil {
		allow = true
	}
	if tc != nil {
		tmp, err := tc.RPC.Call1(g.EventRemoveUser, uc, tc)
		ok := tmp.(bool)
		if err == nil && ok {
			allow = true
		}
		//先同步下注然后再退出
		toSyncBet := atomic.SwapInt64(&uc.UnSync, 0)
		if toSyncBet != 0 {
			level := uc.RobotLevel
			data := &cxt.SyncData{
				UserId:         uc.ID,
				GameType:       tc.GameType,
				RobotLevel:     level,
				Bet:            -toSyncBet,
				LogOut:         true,
				Award:          0,
				TermNumber:     tc.TermNumber,
				TableID:        tc.ID,
				WithdrawalRate: uc.WithdrawalRate,
			}
			g.ServerCloseWG.Add(1)
			cxt.GetTermChannel(uc.ID) <- data
		}
	}
	if allow {
		//仅移除金币锁，内存操作由桌子协程决定
		_ = g.GetPublicCache(uc.GameType).RemoveUser(uc.ID)
		log.Info("user %v logout from %v", uc.ID, g.GetCodeLabel(uc.GameType))
		return
	}
	//否则将用户设为离线，等结算后再关闭agent
	atomic.StoreInt32(&uc.Disconnect, 1)
	log.Info("user %v disconnect from game %v room %v",
		uc.ID, g.GetCodeLabel(uc.GameType), uc.RoomKind)
}
